package tools;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.LineNumberReader;
import java.io.ByteArrayOutputStream;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class GenerateExamples extends org.apache.tools.ant.Task {

  // ----------------------------------------- Class Variables

  protected static final String NL = System.getProperty("line.separator");

  protected static final String JAVAMARKER = "//!";

  protected static final String JAVAMARKER2 = "// !";

  protected static final String BATCHMARKER = "rem !";

  protected static final String MQSCMARKER = "** !";

  protected static final String XMLMARKER = "<!--!";

  protected static final String XMLMARKEREND = "-->";

  protected static final String LINE_START = "<line n=\"x\">";

  protected static final String LINE_END = "</line>";

  protected static final String EXAMPLE_TAGNAME = "example";

  protected static final String CHAPTER_TAGNAME = "c";

  protected static final String SECTION_TAGNAME = "s";

  protected static final String EXNAME_ATTNAME = "xn";

  protected static final String LINE_TAGNAME = "line";

  protected static final String LINENO_ATTNAME = "n";

  // -------------------------------------- Instance Variables

  File javaSrc = null;

  File exampleDir = null;

  org.apache.tools.ant.types.FileSet srcSet = null;

  Transformer genericXformer = null;

  // -------------------------------------------- Constructors

  /**
   * Parse java files to pull out example code and place in special text files
   * for inclusion in the book.
   * 
   * @param file
   *          the java source file to process
   * @param output
   *          the directory where examples are placed
   * 
   * @throws Exception
   *           if any error occurs
   */
  public GenerateExamples(String file, String output) throws Exception {

    javaSrc = new File(file);
    exampleDir = new File(output);
    if (!javaSrc.canRead()) {
      throw new Exception("Cannot read java source file: " + file);
    }
    if (!exampleDir.isDirectory()) {
      throw new Exception("Bad output directory: " + output);
    }
    genericXformer = TransformerFactory.newInstance().newTransformer();
    genericXformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

  }

  /**
   * No arg constructor for the Ant Task
   * 
   * @throws Exception
   *           if any error occurs
   */
  public GenerateExamples() {

    try {
      genericXformer = TransformerFactory.newInstance().newTransformer();
    } catch (Exception e) {
      e.printStackTrace();
      System.exit(0);
    }

  }

  // ------------------------------------------ Public Methods

  public static void main(String[] args) throws Exception {

    if (args.length != 2) {
      System.out.println("Usage: java " + GenerateExamples.class
          + " [file] [output]");
      return;
    }
    GenerateExamples generator = new GenerateExamples(args[0], args[1]);
    try {
      generator.generateExamples();
    } catch (Exception e) {
      e.printStackTrace();
    }

  }

  public void execute() throws org.apache.tools.ant.BuildException {

    try {
      org.apache.tools.ant.Project p = srcSet.getProject();
      org.apache.tools.ant.DirectoryScanner ds = srcSet.getDirectoryScanner(p);
      File baseDir = ds.getBasedir();
      String[] sourceFiles = ds.getIncludedFiles();
      for (int i = 0; i < sourceFiles.length; i++) {
        setJavaSrc(new File(baseDir, sourceFiles[i]));
        generateExamples();
      }
    } catch (Exception e) {
      throw new org.apache.tools.ant.BuildException(e);
    }

  }

  public void generateExamples() throws Exception {

    LineNumberReader reader = new LineNumberReader(new FileReader(javaSrc));
    File temp = new File("genexamples.tmp");
    FileWriter writer = new FileWriter(temp);
    writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + NL);
    writer.write("<srccode>" + NL);
    String line = reader.readLine();
    int javaMarkerIndex = -1;
    int javaMarker2Index = -1;
    int xmlMarkerIndex = -1;
    int batchMarkerIndex = -1;
    int mqscMarkerIndex = -1;
    String modifiedLine = "";
    String escapedLine = "";
    while (line != null) {
      javaMarkerIndex = line.indexOf(JAVAMARKER);
      javaMarker2Index = line.indexOf(JAVAMARKER2);
      xmlMarkerIndex = line.indexOf(XMLMARKER);
      batchMarkerIndex = line.indexOf(BATCHMARKER);
      mqscMarkerIndex = line.indexOf(MQSCMARKER);
      if (javaMarkerIndex >= 0) {
        modifiedLine = line.replaceFirst(JAVAMARKER, "").trim() + NL;
      } else if (javaMarker2Index >= 0) {
        modifiedLine = line.replaceFirst(JAVAMARKER2, "").trim() + NL;
      } else if (xmlMarkerIndex >= 0) {
        modifiedLine = line.replaceFirst(XMLMARKER, "");
        modifiedLine = modifiedLine.replaceFirst(XMLMARKEREND, "").trim() + NL;
      } else if (batchMarkerIndex >= 0) {
        modifiedLine = line.replaceFirst(BATCHMARKER, "").trim() + NL;
      } else if (mqscMarkerIndex >= 0) {
        modifiedLine = line.replaceFirst("\\Q" + MQSCMARKER + "\\E", "").trim()
            + NL;
      } else {
        escapedLine = line.replaceAll("&", "&amp;");
        escapedLine = escapedLine.replaceAll("<", "&lt;");
        escapedLine = escapedLine.replaceAll(">", "&gt;");
        modifiedLine = LINE_START.replaceFirst("x", String.valueOf(reader
            .getLineNumber()))
            + escapedLine + LINE_END + NL;
      }
      writer.write(modifiedLine);
      line = reader.readLine();
    }
    writer.write("</srccode>" + NL);
    writer.close();
    DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance()
        .newDocumentBuilder();
    Document xmlSrc = docBuilder.parse(temp);
    NodeList examples = xmlSrc.getElementsByTagName(EXAMPLE_TAGNAME);
    Element example = null;
    String chapter = null;
    String section = null;
    String exName = null;
    File exampleFile = null;
    FileWriter exampleWriter = null;
    NodeList lines = null;
    Element lineElt = null;
    String lineNo = null;
    int lineNoAsInt;
    String rightJustifiedNo = null;
    String examplePathName = null;
    File examplePath = null;
    String exampleFileName = null;
    for (int i = 0; i < examples.getLength(); i++) {
      example = (Element) examples.item(i);
      chapter = example.getElementsByTagName(CHAPTER_TAGNAME).item(0)
          .getFirstChild().getNodeValue();
      section = example.getElementsByTagName(SECTION_TAGNAME).item(0)
          .getFirstChild().getNodeValue();
      exName = example.getAttribute(EXNAME_ATTNAME);
      examplePathName = "/" + chapter + "/" + section + "/";
      exampleFileName = exName + ".txt";
      examplePath = new File(exampleDir, examplePathName);
      examplePath.mkdirs();
      exampleFile = new File(exampleDir, examplePathName + "/"
          + exampleFileName);
      System.out.println("Creating file: " + exampleFile.getAbsolutePath());
      exampleFile.createNewFile();
      exampleWriter = new FileWriter(exampleFile);
      lines = example.getElementsByTagName(LINE_TAGNAME);
      for (int j = 0; j < lines.getLength(); j++) {
        lineElt = (Element) lines.item(j);
        lineNo = lineElt.getAttribute(LINENO_ATTNAME);
        lineNoAsInt = Integer.parseInt(lineNo);
        if (lineNoAsInt < 10) {
          rightJustifiedNo = "   " + lineNoAsInt + "  ";
        } else if (lineNoAsInt < 100) {
          rightJustifiedNo = "  " + lineNoAsInt + "  ";
        } else if (lineNoAsInt < 1000) {
          rightJustifiedNo = " " + lineNoAsInt + "  ";
        } else {
          rightJustifiedNo = lineNoAsInt + "  ";
        }
        exampleWriter.write(rightJustifiedNo);
        line = ""; // blank line
        if (lineElt.hasChildNodes()) {
          NodeList lineParts = lineElt.getChildNodes();
          for (int k = 0; k < lineParts.getLength(); k++) {
            line += domToString(lineParts.item(k));
          }
        }
        line = line.replaceAll("\t", "        ");
        line = line.replaceAll("&amp;", "&");
        line = line.replaceAll("&lt;", "<");
        line = line.replaceAll("&gt;", ">");
        exampleWriter.write(line + NL);
      }
      // int srcInx = javaSrc.getAbsolutePath().indexOf("src");
      // exampleWriter.write
      // ("source: ... " +
      // javaSrc.getCanonicalPath().substring(srcInx).
      // replaceAll("\\\\","/") + NL);
      int comInx = javaSrc.getAbsolutePath().indexOf("book-code");
      exampleWriter.write(javaSrc.getCanonicalPath().substring(comInx)
          .replaceAll("\\\\", "/")
          + NL);
      exampleWriter.close();
    }

  }

  private String domToString(Node n) throws Exception {

    if (n == null) {
      return null;
    }
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    StreamResult res = new StreamResult(out);
    genericXformer.transform(new DOMSource(n), res);
    String xml = out.toString().replaceAll(
        "<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>", "");
    int indx = xml.indexOf(NL);
    int beginningSecondLine = 0;
    if (indx >= 0) {
      beginningSecondLine = xml.indexOf(NL) + NL.length();
    }
    return xml.substring(beginningSecondLine);

  }

  public void setJavaSrc(File f) {
    javaSrc = f;
  }

  public void setExampleDir(File f) {
    exampleDir = f;
  }

  public void addConfiguredSrcSet(org.apache.tools.ant.types.FileSet fs) {
    srcSet = fs;
  }

}
